#include "swap.h"
#include <stdlib.h>
#include "edgeIDEvaluator.h"
#include "operations.h"

// --- SOLVE  --------------------------------------------------------------------------------------------------------------------

int solveSwapNeighbourhood(Pgraph p_graph, PvehicleState p_state, PGMatrix p_gMat, int k, PtabooList p_tabooList, TabooExpire now, pLOSConfiguration pLOSconfig, bool notWorseningMoves){

	PswapBestChoice pbest; //una struttura contenente la migliore scelta fino a questo momento
	PedgeID toremove1; //il primo arco da eliminare dalla soluzione
	PedgeID toremove2; //il secondo arco da eliminare dalla soluzione
	int toremove1passed; //indica quante volte il camion k passa dall'arco toremove1
	int toremove2passed; //indica quante volte il camion k passa dall'arco toremove2
	PedgeID toadd1; //il prima arco da agguingere nella soluzione
	PedgeID toadd2; //il secondo arco da agguingere nella soluzione
	PedgeID edges[p_state->pathSize]; //archi coinvolti nel percorso per gestire piu' facilmente le combinazioni
	PedgeSol e; //variabile di ciclo
	int i=0; //variabile di ciclo
	int j=0; //variabile di ciclo
	int t=0; //variabile di ciclo
	int currentValutation; //indica quanto profitto dalo swap corrente
	int tqokswap; //determina se la mossa e' tqok oppure no
	int graphproperty; //1 se il grafo e' connesso, 0 altrimenti
	int initialProfit = p_state->jk; //indica il profitto del percorso _pstate prima di effettuare swap
	//inizializzazioni varie
	pbest = initSwapBestChoice();
	toadd1 = initEdgeID(0,0);
	toadd2 = initEdgeID(0,0);

	//prendo tutti gli edgeID contenuti nel percorso del camion k
	resetMemory(p_state->pvp, DIRECTION_NEXT);
	while ((e = getNearEdgeInVehiclePath(p_state->pvp, DIRECTION_NEXT))!=NULL){
		edges[i]=e->id;
		i++;
	}
	//gli archi in edges sono tutti gia' validati. Quindi non servono ulteriori controlli per rivalidarli
	//ora considero tutte le possibli combinazioni di archi
	for (i=0;i<(p_state->pathSize-1);i++){
		toremove1=edges[i];
		toremove1passed=getEdgeWalkedTimesInVehiclePath(toremove1, p_state->pvp);
		for (j=(i+1);j<p_state->pathSize;j++){
			toremove2=edges[j];
			if (equals(toremove1, toremove2)){
				continue;
			}
			if (areConsecutive(toremove1, toremove2)){//elimino quelli consecutivi tra loro
				continue;
			}
			//verifico che gli archi scelti da rimuovere non siano ancora in taboo
			if (isEdgeTaboo(p_tabooList, toremove1, now) && isEdgeTaboo(p_tabooList, toremove2, now)){
				continue;
			}
			toremove2passed=getEdgeWalkedTimesInVehiclePath(toremove2, p_state->pvp);
			//dati i 2 archi A-B e C-D da swappare esistono due mosse swap possibili: A-C,B-D e A-D,B-C
			for (t=0; t<=SWAP_GENERATIONMODE_1; t++){
				generateCrossedEdges(toremove1, toremove2, toadd1, toadd2, t);

				//valuto gli archi
				/*PRINT DEI REMOVE
				printf("toremove1: ");
				printEdgeID(stdout,toremove1);
				printf("\ntoremove2: ");
				printEdgeID(stdout,toremove2);
				printf("\n");*/
				/*PRINT DEI ADD
				printf("toadd1: ");
				printEdgeID(stdout,toadd1);
				printf("\n");
				printf("toadd2: ");
				printEdgeID(stdout,toadd2);
				printf("\n");*/

				//verifico che gli archi scelti da aggiugnere non sia ancora in taboo
				if (isEdgeTaboo(p_tabooList, toadd1, now) || isEdgeTaboo(p_tabooList, toadd1, now)){
					continue;
				}

				//controllo che il percorso senza gli archi "remove" e con l'aggiunta degli archi "add" sia ancora TQOK
				tqokswap = isTQOKSwap(p_graph, p_gMat, k, p_state, toadd1, toadd2, toremove1, toremove1passed, toremove2, toremove2passed);

				/*STAMPA DI TQOK
				printf("isTQOKSwap %d\n",tqokswap);*/
				/*printVehicleAllScalars(stdout,p_state);*/
				if (tqokswap==SWAP_NOTQ_OK){
					continue;
				}
				/*printf("++++++++++++++++++++++++++++++++\n");
				printVehicleState(stdout,p_state,k);*/
				//ora effettuo la verifica piu' complicata: controllo se il grafo e' connesso
				//effettuo la modifica a prescindere
				removeEdgeFromVehicleStateForConnectionTest(toremove1, p_state);
				removeEdgeFromVehicleStateForConnectionTest(toremove2, p_state);
				addEdgeToVehicleStateForConnectionTest(toadd1, p_state);
				addEdgeToVehicleStateForConnectionTest(toadd2, p_state);
				graphproperty = isConnected(p_state->pvp, p_state->lEdges);
				//resetto la mossa a prescindere
				removeEdgeFromVehicleStateForConnectionTest(toadd1, p_state);
				removeEdgeFromVehicleStateForConnectionTest(toadd2, p_state);
				addEdgeToVehicleStateForConnectionTest(toremove1, p_state);
				addEdgeToVehicleStateForConnectionTest(toremove2, p_state);
				//printVehicleState(stdout,p_state,k);
				if (graphproperty==false){
					continue;
				}
				//printf("++++++++++++++++++++++++++++++++++++\n");
				//currentValutation=0;
				//tolgo dalla valutazione gli archi che sto togliendo
				//currentValutation-=!(amIGatheringEdge(p_gMat,toremove1,k)&&(toremove1passed==1))*evaluateEdge(p_graph,toremove1,p_state->tau,p_state->theta,EVAL_WISE);
				//currentValutation-=!(amIGatheringEdge(p_gMat,toremove2,k)&&(toremove1passed==1))*evaluateEdge(p_graph,toremove2,p_state->tau,p_state->theta,EVAL_WISE);

				switch (tqokswap){
				case SWAP_ONLYEDGE1:{
					currentValutation=!isEdgeGathered(p_gMat,toadd1)*evaluateEdge(p_graph,toadd1,p_state->tau,p_state->theta, pLOSconfig->evaluate);
					if (currentValutation>pbest->bestValutation){
						updateSwapBestChoice(pbest,toadd1,toadd2,toremove1,toremove2,currentValutation,true,false);
					}
					break;
				}
				case SWAP_ONLYEDGE2:{
					currentValutation=!isEdgeGathered(p_gMat,toadd2)*evaluateEdge(p_graph,toadd2,p_state->tau,p_state->theta, pLOSconfig->evaluate);
					if (currentValutation>pbest->bestValutation){
						updateSwapBestChoice(pbest,toadd1,toadd2,toremove1,toremove2,currentValutation,false,true);
					}
					break;
				}
				case SWAP_BOTHEDGES: {//il nuovo percorso soddisfa tutti eds entrambi gli archi potrebbero essere raccolti
					currentValutation=
						!isEdgeGathered(p_gMat,toadd1) * evaluateEdge(p_graph,toadd1,p_state->tau,p_state->theta, pLOSconfig->evaluate) +
						!isEdgeGathered(p_gMat,toadd2) * evaluateEdge(p_graph,toadd2,p_state->tau,p_state->theta, pLOSconfig->evaluate);
					if (currentValutation > pbest->bestValutation){
						updateSwapBestChoice(pbest, toadd1, toadd2, toremove1, toremove2, currentValutation, true, true);
					}
					break;
				}
				default: {
					fprintf(stderr,"comportamento anomalo in swap.c; ulteriori informazioni:");
					fprintf(stderr,"tqok valore: %d\n",tqokswap);
					fprintf(stderr,"\ntoremove1: ");
					printEdgeID(stderr,toremove1);
					fprintf(stderr,"\ntoremove2: ");
					printEdgeID(stderr,toremove2);
					fprintf(stderr,"\ntoadd1: ");
					printEdgeID(stderr,toadd1);
					fprintf(stderr,"\ntoadd2: ");
					printEdgeID(stderr,toadd2);
					fprintf(stderr,"\n");
				}
				}
			}
		}
	}
	//controllo che il meglio finora sia stato effettivament etrovato
	//STAMPA DI PBEST
	//printf("Stampa di PBEST in swap.c");
	//printSwapBestChoice(stdout,pbest);
	if (pbest->bestValutation==0){
		return MOVE_STATUS_FAIL;
	}

	if(!notWorseningMoves) { //non importa se sto peggiorando
		makeSwapMove(p_graph, p_state, p_gMat, k, p_tabooList, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
		//libero la memoria dalle allocazioni che ho fatto
		freeSwapBestChoice(pbest);
		freeEdgeID(toadd1);
		freeEdgeID(toadd2);
	}
	if(p_state->jk >= initialProfit) {
		if(notWorseningMoves) {  // importa se sto peggiorando
			makeSwapMove(p_graph, p_state, p_gMat, k, p_tabooList, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
			//libero la memoria dalle allocazioni che ho fatto
			freeSwapBestChoice(pbest);
			freeEdgeID(toadd1);
			freeEdgeID(toadd2);
		}
		if(p_state->jk > initialProfit)
			return MOVE_STATUS_IMPROVE;
		else
			return MOVE_STATUS_UNCHANGED;
	}
	else
		return MOVE_STATUS_WORSEN;
}

void generateCrossedEdges(PedgeID e1, PedgeID e2, PedgeID s1, PedgeID s2, int generationmode){
	switch (generationmode){
	case SWAP_GENERATIONMODE_0: {
		s1->i=e1->i; s1->j=e2->j;
		s2->i=e1->j; s2->j=e2->i;
		break;
	}
	case SWAP_GENERATIONMODE_1: {
		s1->i=e1->i; s1->j=e2->i;
		s2->i=e1->j; s2->j=e2->j;
		break;
	}
	default :{
		fprintf(stderr,"richiamato il valore %d in generateCrossedEdge\n",generationmode);
		return;
	}
	}
	validateEdgeID(s1);
	validateEdgeID(s2);
}

PswapBestChoice initSwapBestChoice(){

	PswapBestChoice result = (PswapBestChoice)malloc(sizeof(SwapBestChoice));
	checkMalloc(result,"swap.c:initSwapBestChoice:1");
	result->bestRemove1.i=0;
	result->bestRemove1.j=0;
	result->bestRemove2.i=0;
	result->bestRemove2.j=0;
	result->bestAdd1.i=0;
	result->bestAdd1.j=0;
	result->bestAdd2.i=0;
	result->bestAdd2.j=0;
	result->bestValutation= 0;
	return result;
}
void freeSwapBestChoice(PswapBestChoice obj){
	free(obj);
}

void printSwapBestChoice(FILE* f,PswapBestChoice obj){

	fprintf(f,"++++++++++Best a the moment:++++++++++++++\n");
	fprintf(f,"toadd1: ");
	printEdgeID(f,&obj->bestAdd1);
	fprintf(f,obj->useAdd1?" TRUE":" FALSE");
	fprintf(f,"\n");
	fprintf(f,"toadd2: ");
	printEdgeID(f,&obj->bestAdd2);
	fprintf(f,obj->useAdd2?" TRUE":" FALSE");
	fprintf(f,"\n");
	fprintf(f,"toremove1: ");
	printEdgeID(f,&obj->bestRemove1);
	fprintf(f,"\n");
	fprintf(f,"toremove2: ");
	printEdgeID(f,&obj->bestRemove2);
	fprintf(f,"\n");
	fprintf(f,"profitto: %d\n",obj->bestValutation);
}

// --- AGGIORNAMENTI ------------------------------------------------------------------------------------------------------------------

void updateSwapBestChoice(PswapBestChoice pbc, PedgeID toadd1, PedgeID toadd2,PedgeID toremove1,PedgeID toremove2, int jk,bool gtoadd1,bool gtoadd2) {
	pbc->bestRemove1.i = toremove1->i; pbc->bestRemove1.j = toremove1->j;
	pbc->bestRemove2.i = toremove2->i; pbc->bestRemove2.j = toremove2->j;
	pbc->bestAdd1.i = toadd1->i; pbc->bestAdd1.j = toadd1->j;
	pbc->bestAdd2.i = toadd2->i; pbc->bestAdd2.j = toadd2->j;
	pbc->useAdd1=gtoadd1;
	pbc->useAdd2=gtoadd2;
	pbc->bestValutation = jk;
}

void makeSwapMove(Pgraph pg,PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now, PswapBestChoice pbest, int addTenure, int removeTenure){
	removeEdgeOp(&pbest->bestRemove1, getEdgeGraph(pg, &pbest->bestRemove1), pvs, pgm, k, ptl, now, removeTenure);
	removeEdgeOp(&pbest->bestRemove2, getEdgeGraph(pg, &pbest->bestRemove2), pvs, pgm, k, ptl, now, removeTenure);
	addEdgeOp(&pbest->bestAdd1, getEdgeGraph(pg, &pbest->bestAdd1), pvs, pgm, k, ptl, now, addTenure, pbest->useAdd1);
	addEdgeOp(&pbest->bestAdd2, getEdgeGraph(pg, &pbest->bestAdd2), pvs, pgm, k, ptl, now, addTenure, pbest->useAdd2);
}

//dato un arco crossato, ti becca l'altro
void getOtherCrossedEdgeID(PedgeID crossededge,PedgeID othercrossededge,int e1i,int e1j,int e2i,int e2j){
	int nodes[2];
	int i=0;
	if (!isNodeUsedByEdgeID(crossededge,e1i)){
		nodes[i]=e1i;
		i++;
	}
	if (!isNodeUsedByEdgeID(crossededge,e1j)){
		nodes[i]=e1j;
		i++;
	}
	if (!isNodeUsedByEdgeID(crossededge,e2i)){
		nodes[i]=e2i;
		i++;
	}
	if (!isNodeUsedByEdgeID(crossededge,e2j)){
		nodes[i]=e2j;
		i++;
	}
	othercrossededge->i=nodes[0];
	othercrossededge->j=nodes[1];
}

//-------- TQ OK CHECKS ------------------------------------------------------------------------------------------------------------

int isTQOKSwap(Pgraph graph,PGMatrix pgm,int k,PvehicleState state,PedgeID toadd1, PedgeID toadd2, PedgeID toremove1,int toremove1passed, PedgeID toremove2,int toremove2passed){
	if (( state->tau+
			getDeltaTimeAddingEdgeID(graph,toadd1)+
			getDeltaTimeAddingEdgeID(graph,toadd2)+
			getDeltaTimeRemovingEdgeID(graph,toremove1)+
			getDeltaTimeRemovingEdgeID(graph,toremove2)
		)> graph->T){//il tempo del nuovo percorso e' maggiore di quello concesso
			return SWAP_NOTQ_OK;
	}
	//il la domanda ed il profitto li devo togliere solo se l'arco era soddisfatto da me e ci passavo una volta sola
	if (( state->theta+
			getDeltaLoadAddingEdgeID(graph,toadd1,pgm)+
			getDeltaLoadAddingEdgeID(graph,toadd2,pgm)+
			getDeltaLoadRemovingEdgeID(graph,toremove1,k,pgm,state)+
			getDeltaLoadRemovingEdgeID(graph,toremove2,k,pgm,state)
		)<= graph->Q){//entrambi i lati swappati possono essere raccolti!
			return SWAP_BOTHEDGES;
	}
	//FIXME in questo modo il primo arco e' piu' "dominante" rispetto al secondo in quanto se si puo' scegliere solo uno tra i due, viene preso prima il primo1
	if (( state->theta+
			getDeltaLoadAddingEdgeID(graph,toadd1,pgm)+
			getDeltaLoadRemovingEdgeID(graph,toremove1,k,pgm,state)+
			getDeltaLoadRemovingEdgeID(graph,toremove2,k,pgm,state)
		)<= graph->Q){//solo il primo lati swappato puo' essere raccolto
			return SWAP_ONLYEDGE1;
	}
	if (( state->theta+
			getDeltaLoadAddingEdgeID(graph,toadd2,pgm)+
			getDeltaLoadRemovingEdgeID(graph,toremove1,k,pgm,state)+
			getDeltaLoadRemovingEdgeID(graph,toremove2,k,pgm,state)
		)<= graph->Q){//solo il secondo lato swappato puo' essere raccolto
			return SWAP_ONLYEDGE2;
	}
	return SWAP_NOTQ_OK;
}
